#include <assert.h>
#include <string.h>
#include "module.h"
#include "mreq.h"
#include "sim.h"
#include "types.h"

extern Simulator Sim;

bool ModuleID::operator== (const ModuleID &mid)
{
    return (this->nodeID == mid.nodeID &&
            this->module_index == mid.module_index);
}

bool ModuleID::operator!= (const ModuleID &mid)
{
    return !(this->nodeID == mid.nodeID &&
             this->module_index == mid.module_index);
}

Module *ModuleID::get_module ()
{
    Module *ret = Sim.Nd[nodeID]->mod[module_index];
    assert (ret != NULL);
    return ret;
}

/************************************************************
 * Module constructor, destructor, and associated functions.
 ************************************************************/
Module::Module (ModuleID moduleID, const char *name, int clock_ratio)
{
    this->moduleID = moduleID;
    this->name = strdup (name);
    this->input_port = new IO_PORT;
    this->clock_ratio = clock_ratio;
    this->num_ports = 4; //TODO : change this into a configurable parameter called the number of cache ports
}

Module::~Module (void)
{
    if (name)
        free (name);

    delete input_port;
}

/** Should this module tick/tock?  */
bool Module::tick_tock_p ()
{
    /** Clock Frequency.  */
    if ((Global_Clock % clock_ratio) == 0)
        return true;
    return false;
}

bool Module::has_waiting_mreq ()
{
    assert (input_port && "read_input_port: NULL port");
    return (!input_port->empty() && input_port->begin()->first <= Global_Clock);
}

/** Read input port. Returns NULL if nothing is available in port.  */
Mreq *Module::read_input_port (void)
{
    Mreq *m;
    IO_PORT::iterator front;
    
    assert (input_port && "read_input_port: NULL port");
    if (input_port->empty())
        return NULL;
    
    front = input_port->begin();

    if (front->first <= Global_Clock)
    {
        if (GENERAL_DEBUG && front->first < Global_Clock)
        {
            if(name != NULL)
                fprintf(stderr, "%s: ", name);
            else 
                fprintf(stderr, "NO_NAME: ");
            
            fprintf (stderr, "Resolve_time < GC? addr: 0x%11x time: %d, GC: %d \n",
                     (unsigned int) front->second->addr, (int) front->first, (int) Global_Clock);
        }
        
        m = front->second;
        
        input_port->erase(front);
	num_ports++;
        assert (m);
        return m;
    }

    return NULL;
}

/** Write to an output port. */
void Module::write_output_port (Mreq *mreq, timestamp_t resolve_time)
{
    assert (mreq && "NULL Mreq being sent?");
    assert (Sim.Nd[moduleID.nodeID] != NULL && "We're calling from a module outside a node?");

    mreq->module_port_resolve_time = resolve_time;
    
    /** Destination is another node, write to network interface.  */
    if (mreq->dest_mid.nodeID != moduleID.nodeID)
    {
        /**NOTE:  Quick hack to make dir look like BROADCAST by removing latency */
        if (mreq->is_no_net_msg)
        {
fatal_error ("in_no_net should be diasbled!");
            /** bypass the network interface, communicate directly to the client!  */
            assert (Sim.Nd[mreq->dest_mid.nodeID]->mod[mreq->dest_mid.module_index] != NULL && "Module doesn't exist at that node!");
            //Sim.Nd[mreq->dest_mid.nodeID]->mod[mreq->dest_mid.module_index]->input_port->insert(std::make_pair (resolve_time, mreq));
            Sim.Nd[mreq->dest_mid.nodeID]->mod[mreq->dest_mid.module_index]->enque(mreq, resolve_time);
        }
        else
            //mreq->print_msg(this->moduleID, "execute send");
            //fprintf(stderr, "resolve_time: %lld\n", resolve_time );
          Sim.Nd[moduleID.nodeID]->mod[NI_M]->enque(mreq, resolve_time);
        //Send((int) NI_M, mreq);//////////////////////////////////////////////////////////////////////////////////////////////////////
    }
    else
    {
        assert (Sim.Nd[moduleID.nodeID]->mod[mreq->dest_mid.module_index] != NULL && "Module doesn't exist in this node!");

        //mreq->print_msg(this->moduleID, "execute send");
        //fprintf(stderr, "resolve_time: %lld\n", resolve_time );
        Sim.Nd[moduleID.nodeID]->mod[mreq->dest_mid.module_index]->enque(mreq, resolve_time);
        //Send((int) mreq->dest_mid.module_index, mreq);////////////////////////////////////////////////////////////////////////////////
#if SIM_WITH_ZESTO
	    Sim.Nd[moduleID.nodeID]->mod[mreq->dest_mid.module_index]->num_ports--;
#endif
    }
}

bool Module::output_port_writable()
{
    assert (Sim.Nd[moduleID.nodeID] != NULL && "We're calling from a module outside a node?");

    assert (Sim.Nd[moduleID.nodeID]->mod[L1_M] != NULL && "Module doesn't exist in this node!");
	if ( Sim.Nd[moduleID.nodeID]->mod[L1_M]->num_ports >0 );
		return true;
    return false;
}

void Module::enque(Mreq *request, timestamp_t resolve_time)
{
//request->print_msg(this->moduleID, "enque no manifold");
//fprintf(stderr, "resolve_time: %lld\n", (long long int) resolve_time);
//fprintf(stderr, "manifold_time: %lld\n", (long long int) manifold::kernel::Manifold::NowTicks());
    input_port->insert(std::make_pair (resolve_time, request));
}

void Module::manifold_enque_now(int unused_id, Mreq *request)
{
request->print_msg(this->moduleID, "enque now using send");
fprintf(stderr, "resolve_time: %lld\n", (long long int) Global_Clock);
fprintf(stderr, "manifold_time: %lld\n", (long long int) manifold::kernel::Manifold::NowTicks());

    input_port->insert(std::make_pair (Global_Clock, request));
}

void print_id (const char *str, ModuleID mid)
{
    switch (mid.module_index) {
    case NI_M: fprintf (stderr, "%4s:%3d/NI  ", str, mid.nodeID); break;
    case PR_M: fprintf (stderr, "%4s:%3d/PR  ", str, mid.nodeID); break;
    case L1_M: fprintf (stderr, "%4s:%3d/L1  ", str, mid.nodeID); break;
    case L2_M: fprintf (stderr, "%4s:%3d/L2  ", str, mid.nodeID); break;
    case L3_M: fprintf (stderr, "%4s:%3d/L3  ", str, mid.nodeID); break;
    case MC_M: fprintf (stderr, "%4s:%3d/MC  ", str, mid.nodeID); break;
    case INVALID_M:  fprintf (stderr, "%4s:%3d/INV ", str, mid.nodeID); break;
    }
}

